package com.qianxun.entrance.filter;

import com.qianxun.framework.common.util.ObjectUtil;
import com.qianxun.framework.common.util.StringUtil;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Response;
import org.apache.catalina.connector.ResponseFacade;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * 请求参数设置
 *
 * @author: huwei
 * @date: 2019/10/17 14:59
 * @version: 1.0.0
 */
@Component
@Order(-9999)
public class RequestParameterSettingFilter implements Filter {
    /**
     * 追加请求参数的文件后缀名
     */
    private final String[] appendParameterFileSuffix = {"html", "htm"};

    /**
     * 不必拦截的url
     */
    private final String[] unSetUrl = {"viewer.html"};

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        //html文件，设置隐藏的参数
        if (isAppendParameterRequest(request.getRequestURI())) {
            StringBuffer requestParam = new StringBuffer();
            request.getParameterMap().forEach((s, strings) -> requestParam.append("\r\n").append("<input class='inContainerSetting-class' name='" + s + "' disable type=\"hidden\" id=\"inContainerSetting-" + s + "\" value=\"" + StringUtil.concatMark(",", strings) + "\">"));
            if (requestParam.length() > 0) {
                RequestParamSettingResponse requestParamSettingResponse = new RequestParamSettingResponse(servletResponse, requestParam.toString());
                filterChain.doFilter(new RequestMethodSetting(servletRequest), requestParamSettingResponse);
                requestParamSettingResponse.remove();
            } else {
                filterChain.doFilter(new RequestMethodSetting(servletRequest), servletResponse);
            }
        } else {
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }

    private boolean isAppendParameterRequest(String uri) {
        for (int i = 0; i < this.appendParameterFileSuffix.length; ++i) {
            if (uri.endsWith(this.appendParameterFileSuffix[i]) && !uri.endsWith(this.unSetUrl[i])) {
                return true;
            }
        }
        return false;
    }

    private class RequestMethodSetting extends RequestFacade {
        public RequestMethodSetting(ServletRequest request) {
            super((Request) ObjectUtil.getFieldValue(request, "request"));
        }

        @Override
        public String getMethod() {
            return RequestMethod.GET.name();
        }
    }

    private class RequestParamSettingResponse extends ResponseFacade {
        private ThreadLocal<RequestParamSettingOutputStream> threadLocal = new InheritableThreadLocal<>();
        ServletResponse servletResponse;
        String requestParam;

        public RequestParamSettingResponse(ServletResponse servletResponse, String requestParam) {
            super((Response) ObjectUtil.getFieldValue(servletResponse, "response"));
            this.servletResponse = servletResponse;
            this.requestParam = requestParam;
        }

        @Override
        public synchronized ServletOutputStream getOutputStream() throws IOException {
            RequestParamSettingOutputStream requestParamSettingOutputStream = threadLocal.get();
            if (requestParamSettingOutputStream == null) {
                threadLocal.set(new RequestParamSettingOutputStream(this.servletResponse.getOutputStream(), this, this.requestParam));
            }
            return this.threadLocal.get();
        }

        @Override
        public void setContentLength(int len) {
            Integer oldLength = getResponse().getContentLength();
            if (oldLength == null || oldLength < len) {
                getResponse().setContentLength(len);
            }
        }

        public void remove() {
            threadLocal.remove();
        }

        private Response getResponse() {
            return (Response) ObjectUtil.getFieldValue(servletResponse, "response");
        }
    }


    private class RequestParamSettingOutputStream extends ServletOutputStream {
        private boolean isFlush = false;
        ServletOutputStream servletOutputStream;
        RequestParamSettingResponse requestParamSettingResponse;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        String requestParam;

        RequestParamSettingOutputStream(ServletOutputStream servletOutputStream
                , RequestParamSettingResponse requestParamSettingResponse, String requestParam) {
            this.servletOutputStream = servletOutputStream;
            this.requestParamSettingResponse = requestParamSettingResponse;
            this.requestParam = requestParam;
        }

        @Override
        public boolean isReady() {
            return this.servletOutputStream.isReady();
        }

        @Override
        public void setWriteListener(WriteListener writeListener) {
            this.servletOutputStream.setWriteListener(writeListener);
        }

        @Override
        public void write(int i) throws IOException {
            this.bos.write(i);
        }

        @Override
        public synchronized void flush() throws IOException {
            if (!isFlush && this.bos.size() > 0) {
                Document document = Jsoup.parse(this.bos.toString());
                this.bos.close();
                Element element = document.select("body").first().children().first();
                if (element == null) {
                    document.select("body").first().append(this.requestParam);
                } else {
                    element.before(this.requestParam);
                }
                //内容处理
                StringBuffer html = new StringBuffer(document.html());
                //预计返回数据总长度为：源文件内容长度 + 设置的参数长度；但是Jsoup在数据解析时，会将table等空白符缩短，所以转化后的长度有可能和预计长度不一致
                //而内容和长度不一致，容易造成内容返回错误；
                int predictCount = requestParamSettingResponse.getResponse().getContentLength() + requestParam.length();
                int realCount = html.length();
                if(realCount < predictCount){
                    int differenceCount = predictCount - realCount;
                    //为当前内容补充空白数据，规避返回错误
                    for(int i = 0 ; i < differenceCount ;++i){
                        html.append(" ");
                    }
                    realCount = predictCount;
                }
                this.requestParamSettingResponse.setContentLength(realCount);
                this.servletOutputStream.write(html.toString().getBytes());
                isFlush = true;
            }
            this.servletOutputStream.flush();
        }
    }

}
